#include "grflib_stdafx.h"
#include "../include/MousePicker.h"
#include "../include/D3D10Engine.h"
#include <Logger.h>
#include <boost/shared_array.hpp>
#include "../include/MsgRouter.h"
#include <iostream>

using namespace grflib::engine::aux;
using namespace grflib::engine::core;

CMousePicker::CMousePicker()
    : m_action(CMousePicker::ACT_NONE),
    m_buttonMask(CMousePicker::BTN_NONE),
    m_currentViewport(0)
{
}

CMousePicker::~CMousePicker()
{
}

HRESULT CMousePicker::OnMouse(bool bLeftButtonDown, bool bRightButtonDown, bool bMiddleButtonDown, 
    bool bSideButton1Down, bool bSideButton2Down, 
    int nMouseWheelDelta, int xPos, int yPos)
{
    HRESULT hr;
    if (ACT_NONE != m_action)
    {
        if (m_buttonMask == BTN_NONE
            || (m_buttonMask & BTN_LEFT) && !bLeftButtonDown
            || (m_buttonMask & BTN_RIGHT) && !bRightButtonDown
            || (m_buttonMask & BTN_MIDDLE) && !bMiddleButtonDown)
            return S_OK;

        //call virtual function to get eye position
        D3DXVECTOR3 vEyePos;
        const D3DXMATRIX *pViewMat;
        const D3DXMATRIX *pProjMat;
        this->GetEyeParams(vEyePos, &pViewMat, &pProjMat);

        //unproject
        V_RETURN( CalcEyeDir(m_vViewDir, vEyePos, pViewMat, pProjMat, xPos, yPos, m_currentViewport) );
        boost::shared_ptr<CSceneObj> pObj;
        
        switch (m_action)
        {
        case ACT_INVOKE_HOOK:
            {
                //call hook function
                if (!m_hookFunc.empty())
                    m_hookFunc(vEyePos, m_vViewDir);
            }
            break;
        case ACT_INVOKE_TARGET_FUNC:
            {
                //call scenegraph to pick obj
                float3 src(vEyePos.x, vEyePos.y, vEyePos.z);
                float3 dir(m_vViewDir.x, m_vViewDir.y, m_vViewDir.z);
                pObj = _SceneMan.GetSceneGraphPtr()->GetIntersectedObj( src, dir );
                //call target OnPick()
                boost::shared_ptr<IPickable> pPicked = boost::dynamic_pointer_cast<IPickable>(pObj);
                if (pPicked) pPicked->OnPick(vEyePos, m_vViewDir);
            }
            break;
        case ACT_NOTIFY_TARGET: //send picking message
            {
                //call scenegraph to pick obj
                float3 src(vEyePos.x, vEyePos.y, vEyePos.z);
                float3 dir(m_vViewDir.x, m_vViewDir.y, m_vViewDir.z);
                pObj = _SceneMan.GetSceneGraphPtr()->GetIntersectedObj( src, dir );
                boost::shared_ptr<CStatedObj> pStatedObj = boost::dynamic_pointer_cast<CStatedObj>(pObj);
                if (!pStatedObj) //is statable
                {
                    //send notification through FSM message
                    std::string strObjName;
                    if (_AIMan.GetObjName(strObjName, pStatedObj))
                    {
                        boost::shared_array<char> pPackage(new char[sizeof(float) * 3 * 2]);
                        float *pf = reinterpret_cast<float *>(pPackage.get());
                        memcpy(pf, (FLOAT *)vEyePos, sizeof(float) * 3);
                        memcpy(pf + 3, (FLOAT *)m_vViewDir, sizeof(float) * 3);
                        _MsgRouter.SendMsg(strObjName.c_str(), "engine_msg:mouse_picked", pPackage, 0);
                    }
                }
            }
            break;
        default:
            break;
        };
    }
    
    return S_OK;
}

void CMousePicker::GetEyeParams(D3DXVECTOR3 &vEyePos,
    const D3DXMATRIX **mView,
    const D3DXMATRIX **mProj)
{
    //get eye position
    boost::shared_ptr<CCamera> &pCam = _CamMan.GetCurrentCamera();
    if (!pCam)
    {
        W32ERR(_T("No active camera!"));
        return;
    }
    vEyePos = *(pCam->GetDXUTCamera()->GetEyePt());
    *mProj = pCam->GetDXUTCamera()->GetProjMatrix();
    *mView = pCam->GetDXUTCamera()->GetViewMatrix();
}


HRESULT CMousePicker::CalcEyeDir(D3DXVECTOR3 &viewDir, 
    const D3DXVECTOR3 &vEyePos, 
    const D3DXMATRIX *mView,
    const D3DXMATRIX *mProj,
    UINT x, UINT y, UINT iViewport)
{
    HRESULT hr;

    UINT nViewports = iViewport + 1;
    D3D10_VIEWPORT *pViewports = new D3D10_VIEWPORT[nViewports];
    if (!pViewports)
        return E_OUTOFMEMORY;

    CEngine::Instance().GetD3DDevice()->RSGetViewports(&nViewports, pViewports);
    if (0 == pViewports[iViewport].Width)
    {
        delete []pViewports;
        W32ERR(_T("Invaild viewport number!"));
        return E_INVALIDARG;
    }

    D3DXVECTOR3 vNear, vFar, vMouse((float)x, (float)y, 0.0f), vDir;
    D3DXMATRIX mWorld;

    D3DXMatrixIdentity(&mWorld);
    D3DXVec3Unproject(&vNear, &vMouse, &pViewports[iViewport], 
                      mProj,
                      mView,
                      D3DXMatrixIdentity(&mWorld));

    D3DXVec3Subtract(&vDir, &vNear, &vEyePos);
    D3DXVec3Normalize(&viewDir, &vDir);

    delete []pViewports;

    return S_OK;
}
